//
// Copyright 2023 The GUAC Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package arangodb

import (
	"context"
	"fmt"
	"strings"

	"github.com/99designs/gqlgen/graphql"
	"github.com/arangodb/go-driver"
	"github.com/guacsec/guac/internal/testing/ptrfrom"
	"github.com/guacsec/guac/pkg/assembler/graphql/model"
	"github.com/guacsec/guac/pkg/assembler/helpers"
)

const noVulnType string = "novuln"

// dbVulnID is the full presentation of the vulnerability node within the arango database
type dbVulnID struct {
	TypeID   string `json:"type_id"`
	VulnType string `json:"type"`
	// VulnID is the ID of the vulnerability node within the database
	VulnID string `json:"vuln_id"`
	// Vuln is the actual vulnerabilityID within the vulnerability ID node within the database
	Vuln string `json:"vuln"`
}

type dbVulnType struct {
	TypeID   string `json:"type_id"`
	VulnType string `json:"type"`
}

func (c *arangoClient) VulnerabilityList(ctx context.Context, vulnSpec model.VulnerabilitySpec, after *string, first *int) (*model.VulnerabilityConnection, error) {
	return nil, fmt.Errorf("not implemented: VulnerabilityList")
}

func (c *arangoClient) Vulnerabilities(ctx context.Context, vulnSpec *model.VulnerabilitySpec) ([]*model.Vulnerability, error) {

	if vulnSpec != nil && vulnSpec.NoVuln != nil && *vulnSpec.NoVuln {
		vulnSpec.Type = ptrfrom.String(noVulnType)
		vulnSpec.VulnerabilityID = ptrfrom.String("")
	}

	if vulnSpec != nil && vulnSpec.ID != nil {
		p, err := c.buildVulnResponseByID(ctx, *vulnSpec.ID, vulnSpec)
		if err != nil {
			return nil, fmt.Errorf("buildVulnResponseByID failed with an error: %w", err)
		}
		return []*model.Vulnerability{p}, nil
	}

	if graphql.HasOperationContext(ctx) {
		// fields: [type vulnerabilityIDs ]
		fields := getPreloads(ctx)

		vulnerabilityRequired := false
		for _, f := range fields {
			if f == vulnerabilityID {
				vulnerabilityRequired = true
			}
		}

		if !vulnerabilityRequired {
			return c.vulnerabilityType(ctx, vulnSpec)
		}
	}

	values := map[string]any{}

	arangoQueryBuilder := setVulnMatchValues(vulnSpec, values)

	arangoQueryBuilder.query.WriteString("\n")
	arangoQueryBuilder.query.WriteString(`RETURN {
		"type_id": vType._id,
		"type": vType.type,
		"vuln_id": vVulnID._id,
		"vuln": vVulnID.vulnerabilityID
	  }`)

	cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "Vulnerabilities")
	if err != nil {
		return nil, fmt.Errorf("failed to query for sources: %w", err)
	}
	defer cursor.Close()

	return getVulnerabilities(ctx, cursor)
}

func (c *arangoClient) vulnerabilityType(ctx context.Context, vulnSpec *model.VulnerabilitySpec) ([]*model.Vulnerability, error) {

	if vulnSpec != nil && vulnSpec.NoVuln != nil && *vulnSpec.NoVuln {
		vulnSpec.Type = ptrfrom.String(noVulnType)
	}

	values := map[string]any{}

	arangoQueryBuilder := newForQuery(vulnTypesStr, "vType")
	if vulnSpec.Type != nil {
		arangoQueryBuilder.filter("vType", "type", "==", "@vulnType")
		values["vulnType"] = strings.ToLower(*vulnSpec.Type)
	}
	arangoQueryBuilder.query.WriteString("\n")
	arangoQueryBuilder.query.WriteString(`RETURN {
		"type_id": vType._id,
		"type": vType.type
	}`)

	cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "vulnerabilityType")
	if err != nil {
		return nil, fmt.Errorf("failed to query for vulnerability type: %w", err)
	}
	defer cursor.Close()

	var vulnerabilities []*model.Vulnerability
	for {
		var doc dbVulnType
		_, err := cursor.ReadDocument(ctx, &doc)
		if err != nil {
			if driver.IsNoMoreDocuments(err) {
				break
			} else {
				return nil, fmt.Errorf("failed to query source type: %w", err)
			}
		} else {
			collectedVuln := &model.Vulnerability{
				ID:               doc.TypeID,
				Type:             doc.VulnType,
				VulnerabilityIDs: []*model.VulnerabilityID{},
			}
			vulnerabilities = append(vulnerabilities, collectedVuln)
		}
	}

	return vulnerabilities, nil
}

func setVulnMatchValues(vulnSpec *model.VulnerabilitySpec, queryValues map[string]any) *arangoQueryBuilder {
	var arangoQueryBuilder *arangoQueryBuilder
	if vulnSpec != nil {
		arangoQueryBuilder = newForQuery(vulnTypesStr, "vType")
		if vulnSpec.Type != nil {
			arangoQueryBuilder.filter("vType", "type", "==", "@vulnType")
			queryValues["vulnType"] = strings.ToLower(*vulnSpec.Type)
		}
		arangoQueryBuilder.forOutBound(vulnHasVulnerabilityIDStr, "vVulnID", "vType")
		if vulnSpec.ID != nil {
			arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
			queryValues["id"] = *vulnSpec.ID
		}
		if vulnSpec.VulnerabilityID != nil {
			arangoQueryBuilder.filter("vVulnID", "vulnerabilityID", "==", "@vulnerabilityID")
			queryValues["vulnerabilityID"] = strings.ToLower(*vulnSpec.VulnerabilityID)
		}
	} else {
		arangoQueryBuilder = newForQuery(vulnTypesStr, "vType")
		arangoQueryBuilder.forOutBound(vulnHasVulnerabilityIDStr, "vVulnID", "vType")
	}
	return arangoQueryBuilder
}

func getVulnQueryValues(vuln *model.VulnerabilityInputSpec) map[string]any {
	values := map[string]any{}
	// add guac keys
	guacIds := helpers.GetKey[*model.VulnerabilityInputSpec, helpers.VulnIds](vuln, helpers.VulnServerKey)
	values["guacVulnKey"] = guacIds.VulnerabilityID
	values["vulnerabilityID"] = strings.ToLower(vuln.VulnerabilityID)
	values["vulnType"] = strings.ToLower(vuln.Type)

	return values
}

func (c *arangoClient) IngestVulnerabilities(ctx context.Context, vulns []*model.IDorVulnerabilityInput) ([]*model.VulnerabilityIDs, error) {
	var listOfValues []map[string]any

	for i := range vulns {
		listOfValues = append(listOfValues, getVulnQueryValues(vulns[i].VulnerabilityInput))
	}

	var documents []string
	for _, val := range listOfValues {
		bs, _ := json.Marshal(val)
		documents = append(documents, string(bs))
	}

	queryValues := map[string]any{}
	queryValues["documents"] = fmt.Sprint(strings.Join(documents, ","))

	var sb strings.Builder

	sb.WriteString("for doc in [")
	for i, val := range listOfValues {
		bs, _ := json.Marshal(val)
		if i == len(listOfValues)-1 {
			sb.WriteString(string(bs))
		} else {
			sb.WriteString(string(bs) + ",")
		}
	}
	sb.WriteString("]")

	query := `
	LET type = FIRST(
		UPSERT { type: doc.vulnType }
		INSERT { type: doc.vulnType }
		UPDATE {}
		IN vulnTypes OPTIONS { indexHint: "byVulnType" }
		RETURN NEW
    )

	LET vuln = FIRST(
	  UPSERT { vulnerabilityID: doc.vulnerabilityID, _parent: type._id , guacKey: doc.guacVulnKey}
	  INSERT { vulnerabilityID: doc.vulnerabilityID, _parent: type._id , guacKey: doc.guacVulnKey}
	  UPDATE {}
	  IN vulnerabilities OPTIONS { indexHint: "byVulnGuacKey" }
	  RETURN NEW
	)

	LET vulnHasVulnerabilityIDCollection = (
	  INSERT { _key: CONCAT("vulnHasVulnerabilityID", type._key, vuln._key), _from: type._id, _to: vuln._id } INTO vulnHasVulnerabilityID OPTIONS { overwriteMode: "ignore" }
	)
	  
    RETURN {
	  "type_id": type._id,
	  "type": type.type,
	  "vuln_id": vuln._id,
	  "vuln": vuln.vulnerabilityID
	}`

	sb.WriteString(query)

	cursor, err := executeQueryWithRetry(ctx, c.db, sb.String(), nil, "IngestVulnerabilities")
	if err != nil {
		return nil, fmt.Errorf("failed to ingest source: %w", err)
	}

	return getVulnerabilityIDs(ctx, cursor)
}

func (c *arangoClient) IngestVulnerability(ctx context.Context, vuln model.IDorVulnerabilityInput) (*model.VulnerabilityIDs, error) {
	query := `
	LET type = FIRST(
		UPSERT { type: @vulnType }
		INSERT { type: @vulnType }
		UPDATE {}
		IN vulnTypes OPTIONS { indexHint: "byVulnType" }
		RETURN NEW
    )

	LET vuln = FIRST(
	  UPSERT { vulnerabilityID: @vulnerabilityID, _parent: type._id , guacKey: @guacVulnKey}
	  INSERT { vulnerabilityID: @vulnerabilityID, _parent: type._id , guacKey: @guacVulnKey}
	  UPDATE {}
	  IN vulnerabilities OPTIONS { indexHint: "byVulnGuacKey" }
	  RETURN NEW
	)

	LET vulnHasVulnerabilityIDCollection = (
	  INSERT { _key: CONCAT("vulnHasVulnerabilityID", type._key, vuln._key), _from: type._id, _to: vuln._id } INTO vulnHasVulnerabilityID OPTIONS { overwriteMode: "ignore" }
	)
	  
    RETURN {
	  "type_id": type._id,
	  "vuln_id": vuln._id
	}`

	cursor, err := executeQueryWithRetry(ctx, c.db, query, getVulnQueryValues(vuln.VulnerabilityInput), "IngestVulnerability")
	if err != nil {
		return nil, fmt.Errorf("failed to ingest cve: %w", err)
	}
	defer cursor.Close()

	createdVulnIDs, err := getVulnerabilityIDs(ctx, cursor)
	if err != nil {
		return nil, fmt.Errorf("failed to get vulnerabilities from arango cursor: %w", err)
	}
	if len(createdVulnIDs) == 1 {
		return createdVulnIDs[0], nil
	} else {
		return nil, fmt.Errorf("number of vulnerabilities ingested is greater than one")
	}
}

func getVulnerabilityIDs(ctx context.Context, cursor driver.Cursor) ([]*model.VulnerabilityIDs, error) {
	var vulnerabilityIDs []*model.VulnerabilityIDs
	for {
		var doc dbVulnID
		_, err := cursor.ReadDocument(ctx, &doc)
		if err != nil {
			if driver.IsNoMoreDocuments(err) {
				break
			} else {
				return nil, fmt.Errorf("failed to get packages from cursor: %w", err)
			}
		} else {
			vulnerabilityIDs = append(vulnerabilityIDs, &model.VulnerabilityIDs{
				VulnerabilityTypeID: doc.TypeID,
				VulnerabilityNodeID: doc.VulnID})
		}
	}
	return vulnerabilityIDs, nil
}

func getVulnerabilities(ctx context.Context, cursor driver.Cursor) ([]*model.Vulnerability, error) {
	vulnTypes := map[string][]*model.VulnerabilityID{}
	var doc dbVulnID
	for {
		_, err := cursor.ReadDocument(ctx, &doc)
		if err != nil {
			if driver.IsNoMoreDocuments(err) {
				break
			} else {
				return nil, fmt.Errorf("failed to get vulnerabilities from cursor: %w", err)
			}
		} else {
			typeString := doc.VulnType + "," + doc.TypeID
			vulnID := &model.VulnerabilityID{
				ID:              doc.VulnID,
				VulnerabilityID: doc.Vuln,
			}
			if _, ok := vulnTypes[typeString]; ok {
				vulnTypes[typeString] = append(vulnTypes[typeString], vulnID)
			} else {
				var vulnIDs []*model.VulnerabilityID
				vulnIDs = append(vulnIDs, vulnID)
				vulnTypes[typeString] = vulnIDs
			}
		}
	}
	var vulnerabilities []*model.Vulnerability
	for vulnType, vulnIDs := range vulnTypes {
		typeValues := strings.Split(vulnType, ",")
		vuln := &model.Vulnerability{
			ID:               typeValues[1],
			Type:             typeValues[0],
			VulnerabilityIDs: vulnIDs,
		}
		vulnerabilities = append(vulnerabilities, vuln)
	}
	return vulnerabilities, nil
}

// Builds a model.Vulnerability to send as GraphQL response, starting from id.
// The optional filter allows restricting output (on selection operations).
func (c *arangoClient) buildVulnResponseByID(ctx context.Context, id string, filter *model.VulnerabilitySpec) (*model.Vulnerability, error) {
	if filter != nil && filter.ID != nil {
		if *filter.ID != id {
			return nil, fmt.Errorf("ID does not match filter")
		}
	}

	if filter != nil && filter.NoVuln != nil && *filter.NoVuln {
		filter.Type = ptrfrom.String(noVulnType)
		filter.VulnerabilityID = ptrfrom.String("")
	}

	idSplit := strings.Split(id, "/")
	if len(idSplit) != 2 {
		return nil, fmt.Errorf("invalid ID: %s", id)
	}

	vl := []*model.VulnerabilityID{}
	if idSplit[0] == vulnerabilitiesStr {
		var foundVulnID *model.VulnerabilityID
		var err error

		foundVulnID, id, err = c.queryVulnIDNodeByID(ctx, id, filter)
		if err != nil {
			return nil, fmt.Errorf("failed to get vulnID node by ID with error: %w", err)
		}
		vl = append(vl, foundVulnID)
	}

	idSplit = strings.Split(id, "/")
	if len(idSplit) != 2 {
		return nil, fmt.Errorf("invalid ID: %s", id)
	}

	var v *model.Vulnerability
	if idSplit[0] == vulnTypesStr {
		var err error
		v, err = c.queryVulnTypeNodeByID(ctx, id, filter, vl)
		if err != nil {
			return nil, fmt.Errorf("failed to get vuln type node by ID with error: %w", err)
		}
	}
	return v, nil
}

func (c *arangoClient) queryVulnIDNodeByID(ctx context.Context, id string, filter *model.VulnerabilitySpec) (*model.VulnerabilityID, string, error) {
	values := map[string]any{}
	arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
	arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
	values["id"] = id
	if filter != nil && filter.VulnerabilityID != nil {
		arangoQueryBuilder.filter("vVulnID", "vulnerabilityID", "==", "@vulnerabilityID")
		values["vulnerabilityID"] = strings.ToLower(*filter.VulnerabilityID)
	}

	arangoQueryBuilder.query.WriteString("\n")
	arangoQueryBuilder.query.WriteString(`RETURN {
		"vuln_id": vVulnID._id,
		"vuln": vVulnID.vulnerabilityID,
		'parent': vVulnID._parent
  	}`)

	cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "queryVulnIDNodeByID")
	if err != nil {
		return nil, "", fmt.Errorf("failed to query for vuln ID: %w, values: %v", err, values)
	}
	defer cursor.Close()

	type parsedVulnId struct {
		// VulnID is the ID of the vulnerability node within the database
		VulnID string `json:"vuln_id"`
		// Vuln is the actual vulnerabilityID within the vulnerability ID node within the database
		Vuln   string `json:"vuln"`
		Parent string `json:"parent"`
	}

	var collectedValues []parsedVulnId
	for {
		var doc parsedVulnId
		_, err := cursor.ReadDocument(ctx, &doc)
		if err != nil {
			if driver.IsNoMoreDocuments(err) {
				break
			} else {
				return nil, "", fmt.Errorf("failed to vuln ID from cursor: %w", err)
			}
		} else {
			collectedValues = append(collectedValues, doc)
		}
	}

	if len(collectedValues) != 1 {
		return nil, "", fmt.Errorf("number of vuln ID nodes found for ID: %s is greater than one", id)
	}

	return &model.VulnerabilityID{
		ID:              collectedValues[0].VulnID,
		VulnerabilityID: collectedValues[0].Vuln,
	}, collectedValues[0].Parent, nil
}

func (c *arangoClient) queryVulnTypeNodeByID(ctx context.Context, id string, filter *model.VulnerabilitySpec, vl []*model.VulnerabilityID) (*model.Vulnerability, error) {
	values := map[string]any{}
	arangoQueryBuilder := newForQuery(vulnTypesStr, "vType")
	arangoQueryBuilder.filter("vType", "_id", "==", "@id")
	values["id"] = id

	if filter != nil && filter.Type != nil {
		arangoQueryBuilder.filter("vType", "type", "==", "@vulnType")
		values["vulnType"] = strings.ToLower(*filter.Type)
	}
	arangoQueryBuilder.query.WriteString("\n")
	arangoQueryBuilder.query.WriteString(`RETURN {
		"type_id": vType._id,
		"type": vType.type
	}`)

	cursor, err := executeQueryWithRetry(ctx, c.db, arangoQueryBuilder.string(), values, "queryVulnTypeNodeByID")
	if err != nil {
		return nil, fmt.Errorf("failed to query for vuln type: %w, values: %v", err, values)
	}
	defer cursor.Close()

	var collectedValues []dbVulnType
	for {
		var doc dbVulnType
		_, err := cursor.ReadDocument(ctx, &doc)
		if err != nil {
			if driver.IsNoMoreDocuments(err) {
				break
			} else {
				return nil, fmt.Errorf("failed to query vuln type: %w", err)
			}
		} else {
			collectedValues = append(collectedValues, doc)
		}
	}

	if len(collectedValues) != 1 {
		return nil, fmt.Errorf("number of vuln type nodes found for ID: %s is greater than one", id)
	}

	return &model.Vulnerability{
		ID:               collectedValues[0].TypeID,
		Type:             collectedValues[0].VulnType,
		VulnerabilityIDs: vl,
	}, nil
}

func (c *arangoClient) vulnTypeNeighbors(ctx context.Context, nodeID string, allowedEdges edgeMap) ([]string, error) {
	out := []string{}
	if allowedEdges[model.EdgeVulnerabilityTypeVulnerabilityID] {
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnTypesStr, "vType")
		arangoQueryBuilder.filter("vType", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forOutBound(vulnHasVulnerabilityIDStr, "vVulnID", "vType")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor: vVulnID._id }")

		foundIDs, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnTypeNeighbors")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundIDs...)
	}
	return out, nil
}

func (c *arangoClient) vulnIdNeighbors(ctx context.Context, nodeID string, allowedEdges edgeMap) ([]string, error) {
	out := []string{}
	if allowedEdges[model.EdgeVulnerabilityIDVulnerabilityType] {
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.query.WriteString("\nRETURN { parent: vVulnID._parent}")

		foundIDs, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundIDs...)
	}
	if allowedEdges[model.EdgeVulnerabilityCertifyVuln] {
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forInBound(certifyVulnEdgesStr, "certifyVuln", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor: certifyVuln._id }")

		foundIDs, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundIDs...)
	}
	if allowedEdges[model.EdgeVulnerabilityVulnEqual] {
		// vulnEqualSubjectVulnEdgesStr collection query
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forOutBound(vulnEqualSubjectVulnEdgesStr, "vulnEqual", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor: vulnEqual._id }")

		foundSubjectIDsOutBound, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors - vulnEqualSubjectVulnEdges outbound")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundSubjectIDsOutBound...)

		values = map[string]any{}
		arangoQueryBuilder = newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forInBound(vulnEqualSubjectVulnEdgesStr, "vulnEqual", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor:  vulnEqual._id }")

		foundSubjectIDsInBound, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors - vulnEqualSubjectVulnEdges inbound")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundSubjectIDsInBound...)

		//vulnEqualVulnEdgesStr collection query

		values = map[string]any{}
		arangoQueryBuilder = newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forOutBound(vulnEqualVulnEdgesStr, "vulnEqual", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor:  vulnEqual._id }")

		foundEqualIDsOutBound, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors - vulnEqualVulnEdges outbound")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundEqualIDsOutBound...)

		values = map[string]any{}
		arangoQueryBuilder = newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forInBound(vulnEqualVulnEdgesStr, "vulnEqual", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor:  vulnEqual._id }")

		foundEqualIDsInBound, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors - vulnEqualVulnEdges inbound")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundEqualIDsInBound...)
	}
	if allowedEdges[model.EdgeVulnerabilityCertifyVexStatement] {
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forInBound(certifyVexVulnEdgesStr, "certifyVex", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor: certifyVex._id }")

		foundIDs, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundIDs...)
	}
	if allowedEdges[model.EdgeVulnMetadataVulnerability] {
		values := map[string]any{}
		arangoQueryBuilder := newForQuery(vulnerabilitiesStr, "vVulnID")
		arangoQueryBuilder.filter("vVulnID", "_id", "==", "@id")
		values["id"] = nodeID
		arangoQueryBuilder.forOutBound(vulnMetadataEdgesStr, "vulnMetadata", "vVulnID")
		arangoQueryBuilder.query.WriteString("\nRETURN { neighbor: vulnMetadata._id }")

		foundIDs, err := c.getNeighborIDFromCursor(ctx, arangoQueryBuilder, values, "vulnIdNeighbors")
		if err != nil {
			return out, fmt.Errorf("failed to get neighbors for node ID: %s from arango cursor with error: %w", nodeID, err)
		}
		out = append(out, foundIDs...)
	}

	return out, nil
}
